{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 投资组合优化\n",
    "\n",
    "本笔记本实现多种投资组合优化方法，包含：\n",
    "- 均值-方差优化(Markowitz模型)\n",
    "- 风险平价模型(Risk Parity)\n",
    "- Black-Litterman模型\n",
    "- 有效前沿分析\n",
    "\n",
    "## 使用说明\n",
    "1. 准备股票代码列表\n",
    "2. 调用get_stock_returns获取收益率数据\n",
    "3. 选择优化方法运行\n",
    "4. 查看优化结果和绩效指标"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import akshare as ak\n",
    "import matplotlib.pyplot as plt\n",
    "import cvxpy as cp\n",
    "from scipy.optimize import minimize\n",
    "\n",
    "# 设置中文显示\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']\n",
    "plt.rcParams['axes.unicode_minus'] = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_stock_returns(stock_codes, start_date='20100101', end_date='20231231'):\n",
    "    \"\"\"\n",
    "    获取多只股票收益率数据\n",
    "    \n",
    "    Args:\n",
    "        stock_codes: 股票代码列表\n",
    "        \n",
    "    Returns:\n",
    "        pd.DataFrame: 收益率数据\n",
    "    \"\"\"\n",
    "    returns = pd.DataFrame()\n",
    "    for code in stock_codes:\n",
    "        try:\n",
    "            df = ak.stock_zh_a_hist(symbol=code, period=\"daily\", \n",
    "                                  start_date=start_date, end_date=end_date, adjust=\"qfq\")\n",
    "            df['日期'] = pd.to_datetime(df['日期'])\n",
    "            df = df.set_index('日期').sort_index()\n",
    "            returns[code] = df['收盘'].pct_change()\n",
    "        except Exception as e:\n",
    "            print(f\"获取股票{code}数据失败: {e}\")\n",
    "    return returns.dropna()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 均值-方差优化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mean_variance_optimization(returns, target_return=None):\n",
    "    \"\"\"\n",
    "    均值-方差优化(Markowitz模型)\n",
    "    \n",
    "    Args:\n",
    "        returns: 收益率数据\n",
    "        target_return: 目标收益率(None表示全局最小方差组合)\n",
    "        \n",
    "    Returns:\n",
    "        dict: 包含权重和绩效指标\n",
    "    \"\"\"\n",
    "    try:\n",
    "        # 计算预期收益率和协方差矩阵\n",
    "        mu = returns.mean().values\n",
    "        Sigma = returns.cov().values\n",
    "        n = len(mu)\n",
    "        \n",
    "        # 定义优化问题\n",
    "        w = cp.Variable(n)\n",
    "        risk = cp.quad_form(w, Sigma)\n",
    "        \n",
    "        if target_return is None:\n",
    "            # 最小方差组合\n",
    "            prob = cp.Problem(cp.Minimize(risk), [cp.sum(w) == 1, w >= 0])\n",
    "        else:\n",
    "            # 给定目标收益率下的最优组合\n",
    "            prob = cp.Problem(cp.Minimize(risk), \n",
    "                             [cp.sum(w) == 1, \n",
    "                              w >= 0, \n",
    "                              mu @ w >= target_return])\n",
    "        \n",
    "        prob.solve()\n",
    "        \n",
    "        # 计算组合绩效\n",
    "        weights = w.value\n",
    "        port_return = mu @ weights\n",
    "        port_vol = np.sqrt(risk.value)\n",
    "        sharpe = port_return / port_vol\n",
    "        \n",
    "        return {\n",
    "            'weights': weights,\n",
    "            'expected_return': port_return,\n",
    "            'volatility': port_vol,\n",
    "            'sharpe_ratio': sharpe\n",
    "        }\n",
    "    except Exception as e:\n",
    "        print(f\"均值-方差优化失败: {e}\")\n",
    "        return None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 风险平价模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def risk_parity_optimization(returns):\n",
    "    \"\"\"\n",
    "    风险平价组合优化\n",
    "    \n",
    "    Args:\n",
    "        returns: 收益率数据\n",
    "        \n",
    "    Returns:\n",
    "        dict: 包含权重和绩效指标\n",
    "    \"\"\"\n",
    "    try:\n",
    "        # 计算协方差矩阵\n",
    "        Sigma = returns.cov().values\n",
    "        n = Sigma.shape[0]\n",
    "        \n",
    "        # 定义优化问题\n",
    "        def risk_parity_objective(w):\n",
    "            w = np.array(w)\n",
    "            sigma = np.sqrt(w.T @ Sigma @ w)\n",
    "            risk_contrib = w * (Sigma @ w) / sigma\n",
    "            target_risk = sigma / n\n",
    "            return np.sum((risk_contrib - target_risk)**2)\n",
    "        \n",
    "        # 约束条件\n",
    "        cons = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1},\n",
    "                {'type': 'ineq', 'fun': lambda w: w})\n",
    "        \n",
    "        # 初始值(等权重)\n",
    "        w0 = np.ones(n) / n\n",
    "        \n",
    "        # 优化\n",
    "        res = minimize(risk_parity_objective, w0, constraints=cons, method='SLSQP')\n",
    "        \n",
    "        # 计算组合绩效\n",
    "        weights = res.x\n",
    "        mu = returns.mean().values\n",
    "        port_return = mu @ weights\n",
    "        port_vol = np.sqrt(weights.T @ Sigma @ weights)\n",
    "        sharpe = port_return / port_vol\n",
    "        \n",
    "        return {\n",
    "            'weights': weights,\n",
    "            'expected_return': port_return,\n",
    "            'volatility': port_vol,\n",
    "            'sharpe_ratio': sharpe\n",
    "        }\n",
    "    except Exception as e:\n",
    "        print(f\"风险平价优化失败: {e}\")\n",
    "        return None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Black-Litterman模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def black_litterman_optimization(returns, P, Q, tau=0.05, delta=2.5):\n",
    "    \"\"\"\n",
    "    Black-Litterman模型\n",
    "    \n",
    "    Args:\n",
    "        returns: 收益率数据\n",
    "        P: 观点矩阵\n",
    "        Q: 观点收益率向量\n",
    "        tau: 信心系数\n",
    "        delta: 风险厌恶系数\n",
    "        \n",
    "    Returns:\n",
    "        dict: 包含权重和绩效指标\n",
    "    \"\"\"\n",
    "    try:\n",
    "        # 计算市场隐含均衡收益率\n",
    "        Sigma = returns.cov().values\n",
    "        n = Sigma.shape[0]\n",
    "        \n",
    "        # 市场均衡权重(假设为市值权重或等权重)\n",
    "        w_mkt = np.ones(n) / n\n",
    "        \n",
    "        # 隐含均衡收益率\n",
    "        Pi = delta * Sigma @ w_mkt\n",
    "        \n",
    "        # 观点不确定性(通常设为对角矩阵)\n",
    "        Omega = np.diag(np.diag(P @ (tau * Sigma) @ P.T))\n",
    "        \n",
    "        # 后验收益率估计\n",
    "        mu_bl = Pi + tau * Sigma @ P.T @ np.linalg.inv(P @ tau * Sigma @ P.T + Omega) @ (Q - P @ Pi)\n",
    "        \n",
    "        # 后验协方差矩阵\n",
    "        Sigma_bl = Sigma + tau * Sigma - tau * Sigma @ P.T @ np.linalg.inv(P @ tau * Sigma @ P.T + Omega) @ P @ tau * Sigma\n",
    "        \n",
    "        # 优化组合权重\n",
    "        w = cp.Variable(n)\n",
    "        risk = cp.quad_form(w, Sigma_bl)\n",
    "        prob = cp.Problem(cp.Maximize(mu_bl @ w - delta/2 * risk), \n",
    "                         [cp.sum(w) == 1, w >= 0])\n",
    "        prob.solve()\n",
    "        \n",
    "        # 计算组合绩效\n",
    "        weights = w.value\n",
    "        port_return = mu_bl @ weights\n",
    "        port_vol = np.sqrt(weights.T @ Sigma @ weights)\n",
    "        sharpe = port_return / port_vol\n",
    "        \n",
    "        return {\n",
    "            'weights': weights,\n",
    "            'expected_return': port_return,\n",
    "            'volatility': port_vol,\n",
    "            'sharpe_ratio': sharpe\n",
    "        }\n",
    "    except Exception as e:\n",
    "        print(f\"Black-Litterman优化失败: {e}\")\n",
    "        return None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 主分析流程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 示例：沪深300成分股\n",
    "stocks = ['600519', '601318', '600036', '601888', '600276']\n",
    "returns = get_stock_returns(stocks)\n",
    "\n",
    "# 均值-方差优化\n",
    "mv_result = mean_variance_optimization(returns)\n",
    "print(\"均值-方差优化结果:\")\n",
    "print(f\"权重: {mv_result['weights']}\")\n",
    "print(f\"预期收益率: {mv_result['expected_return']:.4f}\")\n",
    "print(f\"波动率: {mv_result['volatility']:.4f}\")\n",
    "print(f\"夏普比率: {mv_result['sharpe_ratio']:.4f}\")\n",
    "\n",
    "# 风险平价优化\n",
    "rp_result = risk_parity_optimization(returns)\n",
    "print(\"\\n风险平价优化结果:\")\n",
    "print(f\"权重: {rp_result['weights']}\")\n",
    "print(f\"预期收益率: {rp_result['expected_return']:.4f}\")\n",
    "print(f\"波动率: {rp_result['volatility']:.4f}\")\n",
    "print(f\"夏普比率: {rp_result['sharpe_ratio']:.4f}\")\n",
    "\n",
    "# Black-Litterman优化(示例观点)\n",
    "P = np.array([[1, 0, 0, 0, 0],  # 第一只股票跑赢2%\n",
    "              [0, 0, 1, -1, 0]]) # 第三只比第四只好3%\n",
    "Q = np.array([0.02, 0.03])\n",
    "bl_result = black_litterman_optimization(returns, P, Q)\n",
    "print(\"\\nBlack-Litterman优化结果:\")\n",
    "print(f\"权重: {bl_result['weights']}\")\n",
    "print(f\"预期收益率: {bl_result['expected_return']:.4f}\")\n",
    "print(f\"波动率: {bl_result['volatility']:.4f}\")\n",
    "print(f\"夏普比率: {bl_result['sharpe_ratio']:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 有效前沿分析"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_efficient_frontier(returns):\n",
    "    \"\"\"\n",
    "    绘制有效前沿\n",
    "    \n",
    "    Args:\n",
    "        returns: 收益率数据\n",
    "    \"\"\"\n",
    "    try:\n",
    "        # 计算预期收益率和协方差矩阵\n",
    "        mu = returns.mean().values\n",
    "        Sigma = returns.cov().values\n",
    "        n = len(mu)\n",
    "        \n",
    "        # 生成不同目标收益率下的最优组合\n",
    "        target_returns = np.linspace(mu.min(), mu.max(), 50)\n",
    "        volatilities = []\n",
    "        \n",
    "        for ret in target_returns:\n",
    "            w = cp.Variable(n)\n",
    "            risk = cp.quad_form(w, Sigma)\n",
    "            prob = cp.Problem(cp.Minimize(risk), \n",
    "                             [cp.sum(w) == 1, \n",
    "                              w >= 0, \n",
    "                              mu @ w >= ret])\n",
    "            prob.solve()\n",
    "            volatilities.append(np.sqrt(risk.value))\n",
    "        \n",
    "        # 绘制有效前沿\n",
    "        plt.figure(figsize=(10, 6))\n",
    "        plt.plot(volatilities, target_returns, 'b-', label='有效前沿')\n",
    "        \n",
    "        # 标记各优化方法结果\n",
    "        mv_result = mean_variance_optimization(returns)\n",
    "        plt.scatter(mv_result['volatility'], mv_result['expected_return'], \n",
    "                   color='r', label='均值-方差最优')\n",
    "        \n",
    "        rp_result = risk_parity_optimization(returns)\n",
    "        plt.scatter(rp_result['volatility'], rp_result['expected_return'], \n",
    "                   color='g', label='风险平价')\n",
    "        \n",
    "        plt.title('有效前沿与优化组合')\n",
    "        plt.xlabel('波动率')\n",
    "        plt.ylabel('预期收益率')\n",
    "        plt.legend()\n",
    "        plt.grid()\n",
    "        plt.show()\n",
    "    except Exception as e:\n",
    "        print(f\"绘制有效前沿失败: {e}\")\n",
    "\n",
    "# 绘制有效前沿\n",
    "plot_efficient_frontier(returns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 权重分布可视化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_weights(weights, stock_names):\n",
    "    \"\"\"绘制权重分布图\"\"\"\n",
    "    plt.figure(figsize=(10, 5))\n",
    "    plt.bar(stock_names, weights)\n",
    "    plt.title('组合权重分布')\n",
    "    plt.ylabel('权重')\n",
    "    plt.xticks(rotation=45)\n",
    "    plt.grid(axis='y')\n",
    "    plt.show()\n",
    "\n",
    "# 绘制均值-方差优化权重\n",
    "plot_weights(mv_result['weights'], stocks)\n",
    "\n",
    "# 绘制风险平价权重\n",
    "plot_weights(rp_result['weights'], stocks)\n",
    "\n",
    "# 绘制Black-Litterman权重\n",
    "plot_weights(bl_result['weights'], stocks)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}